/**
 * @Created Nov 7, 2011 1:03:14 PM
 * @author cry30
 */
package com.philip.journal.home.service;

import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.List;
import java.util.Locale;

import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.w3c.dom.Text;

import com.philip.journal.common.BeanUtils;
import com.philip.journal.common.XmlUtils;
import com.philip.journal.core.Messages;
import com.philip.journal.core.Messages.Error;
import com.philip.journal.core.bean.User;
import com.philip.journal.core.exception.JournalException;
import com.philip.journal.core.service.BaseService;
import com.philip.journal.home.bean.Branch;
import com.philip.journal.home.bean.Entry;
import com.philip.journal.home.dao.BranchDAO;
import com.philip.journal.home.dao.EntryDAO;

/**
 *
 */
public class XmlHelper extends BaseService {

    /** XML Tag. RTFC. */
    public static final String TAG_BRANCH = "Branch";
    /** XML Tag. RTFC. */
    public static final String TAG_BRANCHES = "Branches";
    /** XML Tag. RTFC. */
    public static final String TAG_ENTRY = "Entry";
    /** XML Tag. RTFC. */
    public static final String TAG_ENTRIES = "Entries";

    /** Excluded bean properties for xml conversion. */
    static final String[] EXCLUDED_PROPS = {
            "parent",
            "primaryKeyField",
            "branch",
            "treePath" };

    /** Time format. */
    private final transient DateFormat timeFormat = new SimpleDateFormat("HH:mm:ss", Locale.getDefault());

    /** Date format. */
    private final transient DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault());

    /**
     * Recursive function to convert a branch and it's descendants into document.
     *
     * @param doc - Document instance for instantiating Element.
     * @param targetElement - the target Element for the conversion.
     * @param branch - starting branch for the conversion.
     *
     * @exception IllegalArgumentException when any of the 3 parameter is null.
     */
    public void convertNodeToXml(final Document doc, final Element targetElement, final Branch branch) {
        if (doc == null || targetElement == null || branch == null) {
            throw new IllegalArgumentException(Error.IAE_NULL);
        }

        final Element thisBranchElement = doc.createElement(TAG_BRANCH);
        targetElement.appendChild(thisBranchElement);

        attachPropertiesToElement(doc, thisBranchElement, branch);
        //getLogger().debug("convertNodeToXml: " + branch);
        final long currentBranchId = branch.getBranchId();

        final List<Branch> children = getDaoFacade().getBranchDAO().readAllByParent(currentBranchId);
        if (!children.isEmpty()) {
            final Element branchesElement = doc.createElement(TAG_BRANCHES);
            thisBranchElement.appendChild(branchesElement);
            for (final Branch nextSubBranch : children) {
                convertNodeToXml(doc, branchesElement, nextSubBranch);
            }
        }

        final List<Entry> entries = getDaoFacade().getEntryDAO().readAllByBranch(currentBranchId);
        if (!entries.isEmpty()) {
            final Element entriesElement = doc.createElement(TAG_ENTRIES);
            thisBranchElement.appendChild(entriesElement);
            for (final Entry nextEntry : entries) {
                final Element entryElement = doc.createElement(TAG_ENTRY);
                attachPropertiesToElement(doc, entryElement, nextEntry);
                entriesElement.appendChild(entryElement);
            }
        }
    }

    /**
     * Helper method to extract properties of a bean and attach it to a specified target element.
     *
     * @param doc - Document object for creating the Element instance
     * @param targetElement - Element to which the extracted properties will be attached.
     * @param bean - The bean whose properties will be converted into Elements.
     *
     * @exception IllegalArgumentException when any of the 3 parameter is null.
     */
    void attachPropertiesToElement(final Document doc, final Element targetElement, final Object bean) {
        if (doc == null || targetElement == null || bean == null) {
            throw new IllegalArgumentException(Error.IAE_NULL);
        }

        final String[] beanProperties = BeanUtils.getProperties(bean, EXCLUDED_PROPS);
        Element propertyElement;
        for (final String property : beanProperties) {
            //            System.out.println("property: " + property);
            propertyElement = doc.createElement(property);
            Object value = null;
            try {
                value = BeanUtils.getProperty(bean, property);
                if (value instanceof User) {
                    value = ((User) value).getUsername();
                }
            } catch (final Exception criticalError) {
                getLogger().debug(criticalError.getMessage(), criticalError);
            }
            final String stringVal = value == null ? "" : String.valueOf(value);
            //            System.out.println("Value: " + value);
            if (!stringVal.trim().equals("")) {
                final Text textValue = XmlUtils.isCData(stringVal) ? doc.createCDATASection(stringVal) : doc
                        .createTextNode(stringVal);
                targetElement.appendChild(propertyElement);
                propertyElement.appendChild(textValue);
            }
        }
    }

    /**
     * Note: RECURSIVE!
     *
     * Helper method to extract Branch object from Element and send it to DAO for persistence. The branchId will be
     * cleared to make this a new insert.
     *
     * @param branchElement Element representation of the Branch.
     * @param parentId parent ID.
     * @exception JournalException Application specific exception on DAO calls.
     * @throws XPathExpressionException XPath module exception.
     * @exception IllegalArgumentException when branchElement is null.
     */
    public void extractBranchFromDocument(final Element branchElement, final long parentId)
            throws XPathExpressionException
    {

        if (branchElement == null) {
            throw JournalException.wrapperException(new IllegalArgumentException(Error.IAE_NULL));
        }

        final Branch targetBranch = convertElementToBranch(branchElement, parentId);

        final XPath xpath = XPathFactory.newInstance().newXPath();
        final Element branchesElement = (Element) xpath.evaluate(TAG_BRANCHES, branchElement, XPathConstants.NODE);
        if (branchesElement != null) {
            final NodeList subBranchList = (NodeList) xpath.evaluate(TAG_BRANCH, branchesElement,
                    XPathConstants.NODESET);
            for (int i = 0; subBranchList != null && i < subBranchList.getLength(); i++) {
                final Element subBranchElement = (Element) subBranchList.item(i);
                extractBranchFromDocument(subBranchElement, targetBranch.getBranchId());
            }
        }
        extractEntryFromDocument(branchElement, targetBranch);
    }

    /**
     * TODO: Unit Testing. <br/>
     * Refactored out from {@link #extractBranchFromDocument(Element, long)}. Will convert Element to Branch and
     * persist.
     *
     * @param branchElement Branch Element to where we create Entries of.
     * @param targetBranch Branch object of the Branch Element.
     * @throws XPathExpressionException XPath module exception.
     */
    void extractEntryFromDocument(final Element branchElement, final Branch targetBranch)
            throws XPathExpressionException
    {
        final XPath xpath = XPathFactory.newInstance().newXPath();
        final EntryDAO entryDao = getDaoFacade().getEntryDAO();
        final BranchDAO branchDao = getDaoFacade().getBranchDAO();
        final Branch subListParent = branchDao.read(targetBranch.getBranchId());
        final Element entriesElement = (Element) xpath.evaluate(TAG_ENTRIES, branchElement, XPathConstants.NODE);

        if (entriesElement != null) {
            final NodeList entryList = (NodeList) xpath.evaluate(TAG_ENTRY, entriesElement, XPathConstants.NODESET);
            final String[] entryProperties = BeanUtils.getProperties(new Entry(), EXCLUDED_PROPS);
            final Entry entry = new Entry();
            for (int i = 0; entryList != null && i < entryList.getLength(); i++) {
                for (final String property : entryProperties) {
                    final Object val = getElementPropValue((Element) entryList.item(i),
                            BeanUtils.getPropertyType(entry, property), property);
                    if (val != null) {
                        try {
                            org.apache.commons.beanutils.BeanUtils.setProperty(entry, property, val);
                        } catch (final Exception e) {
                            throw new JournalException(e.getMessage(), e);
                        }
                    }
                }
                if (null == entryDao.readByTitle(entry.getTitle(), subListParent.getBranchId())) {
                    entry.setBranch(subListParent);
                    entry.setNodeId(0);
                    entryDao.save(entry);
                }
            }
        }

    }

    /**
     * TODO: Unit test for normal scenario.
     *
     * Refactored out from {@link #extractBranchFromDocument(Element, long)}. Will convert Element to Branch and
     * persist.
     * 
     * @param branchElement Element representation of the Branch.
     * @param parentId parent Branch ID.
     * @return Branch the converted Branch from Element.
     * 
     * @exception JournalException when:
     *                <ul>
     *                <li>Application specific exception on DAO calls.
     *                <li>null branchElement.
     *                <li>Invalid parent ID.
     *                </ul>
     * @throws XPathExpressionException XPath module exception.
     */
    Branch convertElementToBranch(final Element branchElement, final long parentId) throws XPathExpressionException
    {
        final BranchDAO branchDao = getDaoFacade().getBranchDAO();
        final Branch parent = branchDao.read(parentId);
        if (parent == null || branchElement == null) {
            throw JournalException.wrapperException(new IllegalArgumentException(Messages.Error.IAE_NULL));
        }

        Branch targetBranch = new Branch();
        final String[] branchProperties = BeanUtils.getProperties(targetBranch, EXCLUDED_PROPS);
        for (final String property : branchProperties) {
            final Object val = getElementPropValue(branchElement, BeanUtils.getPropertyType(targetBranch, property),
                    property);
            if (val != null) {
                try {
                    org.apache.commons.beanutils.BeanUtils.setProperty(targetBranch, property, val);
                } catch (final Exception e) {
                    throw new JournalException(e.getMessage(), e);
                }
            }
        }

        targetBranch.setParent(parent);
        final Branch storedBranch = branchDao.readByName(targetBranch.getName(), parentId);
        if (storedBranch == null) {
            targetBranch.setBranchId(0);
            branchDao.save(targetBranch);
        } else {
            targetBranch = storedBranch;
        }
        return targetBranch;
    }

    /**
     * I take a xml element and the tag name, look for the tag and get the text content i.e for
     *
     * <pre>
     * <employee>
     *      <name>John
     *
     * Note: Closing tags removed due to complaints from Checkstyle.
     *
     * </pre>
     *
     * xml snippet if the Element points to employee node and tagName is 'name' I will return John.
     *
     * @param element xml element object.
     * @param valType value type for casting to java object.
     * @param property xml property name.
     * @return property value of the element property.
     * @throws XPathExpressionException thrown by XPatch classes.
     */
    Object getElementPropValue(final Element element, final Class<?> valType, final String property)
            throws XPathExpressionException {
        final XPath xpath = XPathFactory.newInstance().newXPath();
        final String xmlString = (String) xpath.evaluate(property, element, XPathConstants.STRING);
        Object val = null;
        if (xmlString != null) {
            final String textVal = xmlString;
            if (Long.TYPE.equals(valType)) {
                val = Long.parseLong(textVal);
            } else if (String.class == valType) {
                val = textVal;
            } else if (property.endsWith("Time")) {
                try {
                    val = timeFormat.parse(textVal);
                } catch (final ParseException ignore) {
                    getLogger().warn(ignore.getMessage(), ignore);
                }
            } else if (property.endsWith("Date")) {
                try {
                    val = dateFormat.parse(textVal);
                } catch (final ParseException ignore) {
                    getLogger().warn(ignore.getMessage(), ignore);
                }
            } else if (User.class == valType) {
                val = getDaoFacade().getUserDAO().readByUsername(textVal);
            }

        }
        return val;
    }
}
